package cn.fanqinghong.ymcc.service.impl;

import cn.fanqinghong.ymcc.constants.CourseOrderContants;
import cn.fanqinghong.ymcc.domain.Course;
import cn.fanqinghong.ymcc.domain.CourseMarket;
import cn.fanqinghong.ymcc.domain.CourseOrder;
import cn.fanqinghong.ymcc.domain.CourseOrderItem;
import cn.fanqinghong.ymcc.dto.CourseOrderCreatParamsDto;
import cn.fanqinghong.ymcc.dto.KillCourseOrderParamDto;
import cn.fanqinghong.ymcc.dto.KillPreOrderDto;
import cn.fanqinghong.ymcc.dto.PayDto;
import cn.fanqinghong.ymcc.exception.GlobalException;
import cn.fanqinghong.ymcc.feignClient.CourseUserLearnFeignClient;
import cn.fanqinghong.ymcc.mapper.CourseOrderMapper;
import cn.fanqinghong.ymcc.result.JSONResult;
import cn.fanqinghong.ymcc.service.ICourseOrderItemService;
import cn.fanqinghong.ymcc.service.ICourseOrderService;
import cn.fanqinghong.ymcc.util.AssertUtil;
import cn.fanqinghong.ymcc.util.CodeGenerateUtils;
import cn.fanqinghong.ymcc.vo.CourseItemVo;
import cn.fanqinghong.ymcc.vo.CourseOrderVo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.enums.IdType;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author superman.fan
 * @since 2022-07-29
 */
@Service
@Slf4j
public class CourseOrderServiceImpl extends ServiceImpl<CourseOrderMapper, CourseOrder> implements ICourseOrderService {

    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    @Autowired
    private CourseUserLearnFeignClient courseUserLearnFeignClient;

    @Autowired
    private ICourseOrderItemService courseOrderItemService;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    /**
     * 课程订单下单
     * @param dto
     * @return
     */
    @Override
    @Transactional
    public String placeOrder(CourseOrderCreatParamsDto dto) {
        //1.参数不为空校验，JSR303已经校验
        //2.查询redis，查看token、是否有效
        //设置登录人，@todo还未做，暂时为1（获取到的唯一token的key）
        Long loginId = 1l;
        //将得到的课程id凭借为以，分割的字符串
        String courseIds = StringUtils.join(dto.getCourseIds(), ",");
        //查询redis的key是否过期
        String key = searchRedis(dto, loginId, courseIds);
        //3.判断课程id是否重复，课程是否是商家的，调用微服务是微服务中就已经查询了
        CourseOrderVo courseOrderVos = getCourseOrderVo(courseIds);
        AssertUtil.isNotNull(courseOrderVos,"信息错误，下单失败");
        List<CourseItemVo> courseInfos = courseOrderVos.getCourseInfos();
        Date date = new Date();
        //保存主订单表
        CourseOrder order = setCourseOrder(dto, loginId, courseOrderVos, date);
        StringBuilder sb = new StringBuilder();
        //保存子订单表
        setCourseOrderItem(courseInfos, date, order, sb);
        //设置主订单表的标题
        order.setTitle(sb.toString());
        //修改订单表
        updateById(order);
        log.info("发布事务消息");
        //传递给支付微服务的参数
        PayDto payDto = setPayDto(loginId, courseIds, order);
        //把数据转为ijson格式交给message传给生产者
        Message<String> message = MessageBuilder.withPayload(JSON.toJSONString(payDto)).build();
        //发送生产者事务消息
        sendProducerMessage(order, message);
        //发布事务消息结束

        //发送延迟消息
        try {
            SendResult sendResult = rocketMQTemplate.syncSend(
                    CourseOrderContants.COURSE_ORDER_CONSUMER_DELAY_TOPIC_TAG,//目的
                    MessageBuilder.withPayload(order.getOrderNo()).build(),
                    4000, //四秒为发送成功就失败
                    4 //30秒延迟
            );
            SendStatus sendStatus = sendResult.getSendStatus();
            if (sendStatus != SendStatus.SEND_OK){
                /*
                   先尝试重试3次，如果3次重试都发不出去
                     1.记录一个log表
                     2.发站内信，发短信，发邮件通知运维人员，人工接入操作
             */
            }
        } catch (Exception e) {
            //发送失败，兜底
            e.printStackTrace();
            //报错，我们需要做补偿机制
            /*
             先尝试重试3次，如果3次重试都发不出去
             1.记录一个log表
             2.发站内信，发短信，发邮件通知运维人员，人工接入操作
             */
        }


        //发送延迟消息结束

        //下单成功，删除当前订单的唯一key
        redisTemplate.delete(key);
        //返回订单号
        return order.getOrderNo();
    }

    /**
     * 传递给支付微服务的参数
     * @param loginId
     * @param courseIds
     * @param order
     * @return
     */
    private PayDto setPayDto(Long loginId, String courseIds, CourseOrder order) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("courseId", courseIds);
        map.put("loginId", loginId);
        PayDto payDto = new PayDto(
                order.getOrderNo(),//订单id
                order.getTotalAmount(),//支付金额
                order.getUserId(),//下单人
                order.getTitle(),//订单标题
                order.getPayType(),//支付方式
                order.getId(),//关联id
                JSON.toJSONString(map)//额外参数
                );
        return payDto;
    }

    /**
     * 发送事务消息，并且验证事务发送是否成功
     * @param order
     * @param message
     */
    private void sendProducerMessage(CourseOrder order, Message<String> message) {
        //发布事务消息
        TransactionSendResult transactionSendResult = rocketMQTemplate.sendMessageInTransaction(
                CourseOrderContants.COURSE_ORDER_PRODUCER_GROUP,
                CourseOrderContants.COURSE_ORDER_PRODUCER_TOPIC_TAG,
                message,
                order
        );
        log.info("检查事务消息执行情况");
        //验证事务发送结果
        LocalTransactionState localTransactionState = transactionSendResult.getLocalTransactionState();
        SendStatus sendStatus = transactionSendResult.getSendStatus();
        if (localTransactionState != LocalTransactionState.COMMIT_MESSAGE || sendStatus != SendStatus.SEND_OK ){
            log.info("事务消息发送异常");
            throw new GlobalException("下单异常，请重新下单");
        }
    }

    /**
     * 通过订单号查询数据
     * @param orderNo
     * @return
     */
    @Override
    public CourseOrder selectCourseOrder(String orderNo){
        CourseOrder courseOrder = new CourseOrder();
        Wrapper<CourseOrder> wrapper = new EntityWrapper<>();
        wrapper.eq("order_no",orderNo);
        return selectOne(wrapper);
    }



    /**
     * 保存主订单和子订单表
     * @param courseOrder
     */
    @Override
    public void savaOrderAndItemOrder(CourseOrder courseOrder) {
        //通过订单号查询数据
        String orderNo = courseOrder.getOrderNo();
        CourseOrder order = selectCourseOrder(orderNo);
        AssertUtil.isNull(order,"订单已经存在");
        //保存主订单表
        insert(courseOrder);
        //获取所有的子订单信息
        List<CourseOrderItem> items = courseOrder.getItems();
        items.forEach(item ->{
            item.setOrderId(courseOrder.getId());
            //保存子订单
            courseOrderItemService.insert(item);
        });
    }



    /**
     * 查询redis的key是否过期
     * @param dto
     * @param loginId
     * @param courseIds
     * @return
     */
    private String searchRedis(CourseOrderCreatParamsDto dto, Long loginId, String courseIds) {
        String key = courseIds + ":" + loginId;
        //根据key查询当前订单是否过期
        Object tokenTemp = redisTemplate.opsForValue().get(key);
        AssertUtil.isNotNull(tokenTemp,"订单已经过期,请重新下单");
        AssertUtil.isEquals(dto.getToken(),tokenTemp.toString(),"订单错误，请重新下单");
        return key;
    }

    /**
     * 获取到当前登陆人购买的课程的信息
     * 通过调用微服务
     * @param courseIds 课程id
     * @return
     */
    private CourseOrderVo getCourseOrderVo(String courseIds) {
        //调用微服务查询课程数据
        JSONResult jsonResult = courseUserLearnFeignClient.courseOrderInfo(courseIds);
        //得到课程数据
        Object data = jsonResult.getData();
        AssertUtil.isTrue(jsonResult.isSuccess(),"服务不可达，下单失败");
        AssertUtil.isNotNull(data,"服务不可达，下单失败");
        //将可课程数据转为相应的格式
        CourseOrderVo courseOrderVos = JSONObject.parseObject(JSONObject.toJSONString(data), CourseOrderVo.class);
        return courseOrderVos;
    }

    /**
     * 保存子订单表
     * @param courseInfos
     * @param date 当前时间
     * @param order 订单主表数据
     * @param sb 拼接的字符串
     */
    private void setCourseOrderItem(List<CourseItemVo> courseInfos, Date date, CourseOrder order, StringBuilder sb) {

        sb.append("【");

        courseInfos.forEach(courseInfo ->{

            Course course = courseInfo.getCourse();
            CourseMarket courseMarket = courseInfo.getCourseMarket();

            CourseOrderItem courseOrderItem = new CourseOrderItem();
//            courseOrderItem.setOrderId(order.getId());
            //订单的课程id
            courseOrderItem.setCourseId(course.getId());
            //订单的单价
            courseOrderItem.setAmount(courseMarket.getPrice());
            //课程数量
            courseOrderItem.setCount(1);
            //下单时间
            courseOrderItem.setCreateTime(date);
            //设置订单编号
            courseOrderItem.setOrderNo(order.getOrderNo());
            //课程名字
            courseOrderItem.setCourseName(course.getName());
            //课程封面
            courseOrderItem.setCoursePic(course.getPic());
            //下单状态
            courseOrderItem.setVersion(order.getVersion());
            sb.append(course.getName()).append(",");
            //设置订单秒杀数量
            order.setTotalCount(courseOrderItem.getCount() + 1);
            //把子订单数据封装到主订单当中，以便于事务消息的发送
            List<CourseOrderItem> items = order.getItems();
            items.add(courseOrderItem);
        });
        sb.append("】订单");
    }

    /**
     * 保存订单主表
     * @param dto 传入的数据
     * @param loginId   当前登陆人
     * @param courseOrderVos 获取到的当前课程信息
     * @param date 当前时间
     * @return
     */
    private CourseOrder setCourseOrder(CourseOrderCreatParamsDto dto, Long loginId, CourseOrderVo courseOrderVos, Date date) {
        CourseOrder order = new CourseOrder();
        order.setCreateTime(date);
        //创建一个唯一的id，订单的id,使用当前时间拼接随机串
        SimpleDateFormat simple = new SimpleDateFormat("yyyyMMddHHmmss");
        simple.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
        String now5 = simple.format(new Date());
        order.setOrderNo(now5+CodeGenerateUtils.generateOrderSn(loginId));
        //总金额
        order.setTotalAmount(courseOrderVos.getTotalAmount());
        //订单状态
        order.setStatusOrder(0);
        //下单人
        order.setUserId(loginId);
        order.setTitle(null);
        //订单状态
        order.setVersion(0);
        //支付类型
        order.setPayType(dto.getPayType());
        return order;
    }


    /**
     * 通过订单号查询主订单表和子订单表
     * @param orderNo
     * @return
     */
    @Override
    public CourseOrder selectOrderAndItemOrder(String orderNo) {
        Wrapper<CourseOrder> wrapper = new EntityWrapper<>();
        wrapper.eq("order_no",orderNo);
        return selectOne(wrapper);
    }


    /**
     * 支付完成修改订单状态
     * @param courseOrder
     */
    @Override
    public void payCourseOrder(CourseOrder courseOrder) {
        courseOrder.setStatusOrder(CourseOrder.COURSE_ORDER_PAY_SUCCESS);
        courseOrder.setUpdateTime(new Date());
        updateById(courseOrder);
    }

    /**
     * 取消订单
     * 1.
     * @param orderNo
     */
    @Override
    public void cancelOrder(String orderNo) {
        AssertUtil.isNotNull(orderNo,"取消订单失败");
        //查询订单
        CourseOrder courseOrder = selectCourseOrder(orderNo);
        AssertUtil.isNotNull(courseOrder,"订单有误");
        //判断订单是否支付成功，支付成功代表未超时
        if (courseOrder.getStatusOrder().intValue() != CourseOrder.COURSE_ORDER_PAY_SUCCESS.intValue()){
            //进入代表超时
            courseOrder.setStatusOrder(CourseOrder.COURSE_ORDER_PAY_OUTTIME_CANCEL);
            updateById(courseOrder);
            //得到预创订单
            KillPreOrderDto preOrder = (KillPreOrderDto)redisTemplate.opsForValue().get(orderNo);
            //判断是否存在预创订单
            if (preOrder != null){//代表秒杀
                log.info("修改预创订单");
                //redis存储活动的信号量
                String key = preOrder.getActivityId() +":" + preOrder.getKillCourseId();
                //得到信号量
                RSemaphore semaphore = redissonClient.getSemaphore(key);
                Integer killCount = 1;
                //增加相同课程数量的信号量
                semaphore.addPermits(killCount);
                //删除预创订单
                redisTemplate.delete(orderNo);
            }
        }
    }

    /**
     * 秒杀下单
     * @param dto
     * @return
     */
    @Override
    public String killPlaceOrder(KillCourseOrderParamDto dto) {
        AssertUtil.isNotNull(dto,"秒杀订单错误");
        //1.参数不为空校验，JSR303已经校验
        //2.查询redis，查看token、是否有效
        //查询redis中的预创建订单
        KillPreOrderDto proOrder = (KillPreOrderDto)redisTemplate.opsForValue().get(dto.getOrderNo());
        AssertUtil.isNotNull(proOrder,"秒杀订单错误，请稍后再试");
        //查询redis的key是否过期
        Long loginId = 1l;//token暂时设置为的登录暂时设置为1
        String key = proOrder.getCourseId() + ":" + loginId;
        //根据key查询当前订单是否过期
        Object tokenTemp = redisTemplate.opsForValue().get(key);
        AssertUtil.isNotNull(tokenTemp,"订单已经过期,请重新下单");
        AssertUtil.isEquals(dto.getToken(),tokenTemp.toString(),"订单错误，请重新下单");
        //3.判断课程id是否重复，课程是否是商家的，调用微服务是微服务中就已经查询了
        //将可课程数据转为相应的格式
        //保存主订单表
        Date date = new Date();
        CourseOrder order = new CourseOrder();
        order.setTotalCount(proOrder.getKillCount());
        order.setTitle("秒杀订单：【"+proOrder.getCourseName()+"】");
        order.setPayType(dto.getPayType());
        order.setStatusOrder(0);
        order.setVersion(0);
        order.setTotalAmount(proOrder.getKillAmount());
        order.setUserId(proOrder.getLoginId());
        order.setOrderNo(dto.getOrderNo());
        order.setCreateTime(date);
        //保存子订单表
        CourseOrderItem orderItem = new CourseOrderItem();
        orderItem.setCoursePic(proOrder.getPic());
        orderItem.setCourseName(proOrder.getCourseName());
        orderItem.setOrderNo(dto.getOrderNo());
        orderItem.setCreateTime(date);
        orderItem.setVersion(order.getVersion());
        orderItem.setCount(proOrder.getKillCount());
        orderItem.setCourseId(proOrder.getCourseId());
        orderItem.setAmount(proOrder.getKillAmount());
        order.getItems().add(orderItem);

        //设置主订单表的标题
        //修改订单表
        log.info("发布事务消息");
        //传递给支付微服务的参数
        HashMap<String, Object> map = new HashMap<>();
        map.put("courseId", proOrder.getCourseId());
        map.put("loginId", proOrder.getLoginId());
        PayDto payDto = new PayDto(
                order.getOrderNo(),//订单id
                order.getTotalAmount(),//支付金额
                order.getUserId(),//下单人
                order.getTitle(),//订单标题
                order.getPayType(),//支付方式
                order.getId(),//关联id
                JSON.toJSONString(map)//额外参数
        );
        //把数据转为ijson格式交给message传给生产者
        Message<String> message = MessageBuilder.withPayload(JSON.toJSONString(payDto)).build();
        //发送生产者事务消息
        sendProducerMessage(order, message);
        //发布事务消息结束

        //发送延迟消息
        try {
            SendResult sendResult = rocketMQTemplate.syncSend(
                    CourseOrderContants.COURSE_ORDER_CONSUMER_DELAY_TOPIC_TAG,//目的
                    MessageBuilder.withPayload(payDto.getOrderNo()).build(),
                    4000, //四秒为发送成功就失败
                    4 //30秒延迟
            );
            SendStatus sendStatus = sendResult.getSendStatus();
            if (sendStatus != SendStatus.SEND_OK){
                /*
                   先尝试重试3次，如果3次重试都发不出去
                     1.记录一个log表
                     2.发站内信，发短信，发邮件通知运维人员，人工接入操作
             */
            }
        } catch (Exception e) {
            //发送失败，兜底
            e.printStackTrace();
            //报错，我们需要做补偿机制
            /*
             先尝试重试3次，如果3次重试都发不出去
             1.记录一个log表
             2.发站内信，发短信，发邮件通知运维人员，人工接入操作
             */
        }


        //发送延迟消息结束


        //支付成功删除预创订单
//

        //下单成功，删除当前订单的唯一key
        redisTemplate.delete(key);
        //返回订单号
        return order.getOrderNo();
    }
}
